package cn.flying.cloud.flow.core.service;

import javax.annotation.Resource;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.ReceiveTask;
import org.flowable.bpmn.model.ScriptTask;
import org.flowable.bpmn.model.ServiceTask;
import org.flowable.bpmn.model.Task;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.HistoryService;
import org.flowable.engine.IdentityService;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.NativeProcessDefinitionQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.flowable.idm.api.Group;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import cn.flying.cloud.base.common.enums.RtCodeEnum;
import cn.flying.cloud.base.common.exception.BizException;
import cn.flying.cloud.base.common.utils.Ft;
import cn.flying.cloud.base.common.utils.lang.StringUtil;
import cn.flying.cloud.base.common.vo.PageRequest;
import cn.flying.cloud.base.common.vo.PageResponse;
import cn.flying.cloud.flow.bo.FwProcInfoBo;
import cn.flying.cloud.flow.bo.FwProcNodeBo;
import cn.flying.cloud.flow.model.ProcDefinition;
import cn.flying.cloud.flow.service.FwProcInfoService;
import cn.flying.cloud.flow.service.FwProcNodeService;

/**
 * 流程定义服务
 *
 * @author: admin
 * @date: 2023年02月16日 10:45
 * @version: 1.0
 */
@Service
public class ProcessDefinitionService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    private ProcessEngine processEngine;
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private HistoryService historyService;
    @Resource
    private IdentityService identityService;
    @Resource
    private FwProcInfoService procInfoService;
    @Resource
    private FwProcNodeService procNodeService;

    /**
     * 查询流程部署定义列表
     *
     * @param pageRequest
     * @return
     */
    public PageResponse<ProcDefinition> getProcessDefinitionPageList(PageRequest<ProcDefinition> pageRequest) {
        Integer pageNum = pageRequest.getPageNum();
        Integer pageSize = pageRequest.getPageSize();
        ProcDefinition param = pageRequest.getParam();
//        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        String sql = " from ACT_RE_PROCDEF RES, ACT_RE_DEPLOYMENT RED WHERE RES.DEPLOYMENT_ID_ = RED.ID_ ";
        if (StringUtil.isNotBlank(param.getTenantId())) {
//            processDefinitionQuery.processDefinitionTenantId(param.getTenantId());
            sql = sql + " AND RES.TENANT_ID_ = '" + param.getTenantId() + "' ";
        }
        if (StringUtil.isNotBlank(param.getId())) {
//            processDefinitionQuery.processDefinitionId(param.getId());
            sql = sql + " AND RES.ID_ = '" + param.getId() + "' ";
        }
        if (StringUtil.isNotBlank(param.getKey())) {
//            processDefinitionQuery.processDefinitionKeyLike("%" + param.getKey() + "%");
            sql = sql + " AND RES.KEY_ like '%" + param.getKey() + "' ";
        }
        if (StringUtil.isNotBlank(param.getName())) {
//            processDefinitionQuery.processDefinitionNameLike("%" + param.getName() + "%");
            sql = sql + " AND RES.NAME_ like '%" + param.getName() + "' ";
        }
        sql = sql + "order by RED.DEPLOY_TIME_ desc ";

//        long count = processDefinitionQuery.count();
//        if (count == 0) {
//            return new PageResponse(0, pageNum, new ArrayList());
//        }
//        processDefinitionQuery.orderByProcessDefinitionKey().orderByDeploymentId().desc();
//        List<ProcessDefinition> definitions = processDefinitionQuery.listPage((pageNum - 1) * pageSize, pageSize);

        NativeProcessDefinitionQuery processDefinitionQuery = repositoryService.createNativeProcessDefinitionQuery();
        long count = processDefinitionQuery.sql("SELECT count(*) " + sql).count();
        if (count == 0) {
            return new PageResponse<>(0, pageNum, new ArrayList<>());
        }
        List<ProcessDefinition> definitions = processDefinitionQuery.sql("SELECT RES.* " + sql).listPage((pageNum - 1) * pageSize, pageSize);

        Iterator<ProcessDefinition> iterator = definitions.iterator();
        // 转换映射关系
        ProcDefinition definition;
        List<ProcDefinition> list = new ArrayList<>();
        while (iterator.hasNext()) {
            ProcessDefinition df = iterator.next();
            definition = new ProcDefinition();
            definition.setId(df.getId());
            definition.setCategory(df.getCategory());
            definition.setKey(df.getKey());
            definition.setName(df.getName());
            definition.setDescription(df.getDescription());
            definition.setResource(df.getResourceName());
            definition.setDiagramResourceName(df.getDiagramResourceName());
            definition.setVersion(String.valueOf(df.getVersion()));
            definition.setSuspended(df.isSuspended());
            definition.setDeploymentId(df.getDeploymentId());
            definition.setTenantId(df.getTenantId());
            Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(df.getDeploymentId()).singleResult();
            definition.setCreateTime(deployment.getDeploymentTime());

            list.add(definition);
        }
        return new PageResponse<>(count, pageNum, list);
    }

    /**
     * 部署流程定义
     *
     * @param file
     * @param key
     * @param tenantId
     * @return
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public String deploy(MultipartFile file, String key, String tenantId) throws Exception {
        Optional.ofNullable(tenantId).orElseThrow(() -> new BizException(RtCodeEnum.R10009));
        String originalFilename = file.getOriginalFilename();
        logger.info("部署流程图【{}】", originalFilename);
        InputStream inputStream = file.getInputStream();
        Deployment deployment = repositoryService.createDeployment()
                .addInputStream(originalFilename, inputStream)
                .key(key).name(originalFilename).tenantId(tenantId)
                .deploy();
        logger.info("部署流程成功！流程部署ID；【{}】，流程部署key：【{}】，流程部署名称：【{}】，流程部署时间：【{}】",
                deployment.getId(), deployment.getKey(), deployment.getName(), Ft.DateFormat.format(deployment.getDeploymentTime(), "yyyy-MM-dd HH:mm:ss"));

        //解析流程节点保存
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        if (StringUtil.isNotBlank(tenantId)) {
            processDefinitionQuery.processDefinitionTenantId(tenantId);
        }
        ProcessDefinition definition = processDefinitionQuery.deploymentId(deployment.getId()).singleResult();

        FwProcInfoBo procInfo = new FwProcInfoBo();
        procInfo.setProcDefId(definition.getId());
        procInfo.setProcDefKey(definition.getKey());
        procInfo.setProcDefName(definition.getName());
        procInfo.setProcDefDesc(definition.getDescription());
        procInfo.setCategory(definition.getCategory());
        procInfo.setVersion(definition.getVersion() + "");
        procInfo.setResource(definition.getResourceName());
        procInfo.setSuspended("0");
        procInfo.setDiagram(definition.getDiagramResourceName());
        procInfo.setDeployId(definition.getDeploymentId());
        procInfo.setDeployTime(deployment.getDeploymentTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());
        procInfo.setRemark("");
        procInfo.setTenantId(tenantId);
        procInfoService.createObj(procInfo);

        List<FwProcNodeBo> nodes = getAllNode(definition.getId());
        for (FwProcNodeBo node : nodes) {
            node.setProcDefId(definition.getId());
            node.setProcDefKey(definition.getKey());
            node.setDeploymentId(definition.getDeploymentId());
            node.setVersion(String.valueOf(definition.getVersion()));
            node.setTenantId(definition.getTenantId());
            procNodeService.createObj(node);
        }

//        //将上一个版本配置的节点权限复制与新版本建立关联
//        addTaskCandidate(definition.getId());
        return definition.getId();
    }

    /**
     * 解析流程图，保存所有的节点
     *
     * @param processDefId
     * @return
     */
    private List<FwProcNodeBo> getAllNode(String processDefId) {
        List<FwProcNodeBo> list = new ArrayList<>();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefId);
        Process process = bpmnModel.getProcessById(processDefId.split(":")[0]);
        Collection<FlowElement> flowElements = process.getFlowElements();
        Iterator<FlowElement> iterator = flowElements.iterator();
        FwProcNodeBo node;

        Task task;
        // 目前暂且先解析这些类型
        while (iterator.hasNext()) {
            node = new FwProcNodeBo();
            FlowElement flowElement = iterator.next();
            if (flowElement instanceof UserTask) {
                task = (UserTask) flowElement;
                node.setNodeId(task.getId());
                node.setNodeName(task.getName());
                node.setNodeType("UserTask");
                node.setRemark(task.getDocumentation());
                list.add(node);
            }
            if (flowElement instanceof ServiceTask) {
                task = (ServiceTask) flowElement;
                node.setNodeId(task.getId());
                node.setNodeName(task.getName());
                node.setNodeType("ServiceTask");
                node.setRemark(task.getDocumentation());
                list.add(node);
            }
            if (flowElement instanceof ReceiveTask) {
                task = (ReceiveTask) flowElement;
                node.setNodeId(task.getId());
                node.setNodeName(task.getName());
                node.setNodeType("ReceiveTask");
                node.setRemark(task.getDocumentation());
                list.add(node);
            }
            if (flowElement instanceof ScriptTask) {
                task = (ScriptTask) flowElement;
                node.setNodeId(task.getId());
                node.setNodeName(task.getName());
                node.setNodeType("ScriptTask");
                node.setRemark(task.getDocumentation());
                list.add(node);
            }
        }
        return list;
    }

//    /**
//     * 添加节点权限
//     *
//     * @param processDefId
//     */
//    private void addTaskCandidate(String processDefId) {
//        String[] keys = processDefId.split(":");
//        //查询本次版本的上一次定义文件
//        List<ProcessDefinition> definitions = repositoryService.createProcessDefinitionQuery()
//                .processDefinitionKey(keys[0])
//                .processDefinitionVersionLowerThan(Integer.valueOf(keys[1]))
//                .orderByProcessDefinitionVersion().desc()
//                .list();
//        if (CollectionUtils.isEmpty(definitions)) {
//            return;
//        }
//        ProcessDefinition definition = definitions.get(0);
//        taskCandidateService.addTaskCandidateFromLatest(processDefId, definition.getId());
//    }

    /**
     * 删除流程定义
     * 会同时删除流程实例
     *
     * @param deploymentId
     * @param tenantId
     */
    @Transactional(rollbackFor = Exception.class)
    public void delete(String deploymentId, String tenantId, boolean cascade) {
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        if (StringUtil.isNotBlank(tenantId)) {
            processDefinitionQuery.processDefinitionTenantId(tenantId);
        }
        ProcessDefinition definition = processDefinitionQuery.deploymentId(deploymentId).singleResult();
        Optional.ofNullable(definition).orElseThrow(() -> new BizException(RtCodeEnum.R90000.getCode(), "要删除的流程定义不存在！"));
        List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery().processDefinitionId(definition.getId()).unfinished().list();
        if (!CollectionUtils.isEmpty(list)) {
            throw new BizException(RtCodeEnum.R90000.getCode(), "不能删除有正在运行的流程实例的流程定义！");
        }
        // 参数cascade=true，表示级联删除这个部署下的所有流程定义，资源，实例，任务，定时任务，历史
        repositoryService.deleteDeployment(deploymentId, cascade);
        // 同时删除节点信息
        FwProcNodeBo node = new FwProcNodeBo();
        node.setProcDefId(definition.getId());
        procNodeService.deleteObj(node);
        logger.info("删除流程成功，部署ID：【{}】，租户：【{}】", deploymentId, tenantId);
    }

    /**
     * 挂起流程
     *
     * @param processDefId
     * @param tenantId
     */
    @Transactional(rollbackFor = Exception.class)
    public void suspend(String processDefId, String tenantId) {
        String[] keys = processDefId.split(":");
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().processDefinitionKey(keys[0]).processDefinitionVersion(Integer.parseInt(keys[1]));
        if (StringUtil.isNotBlank(tenantId)) {
            processDefinitionQuery.processDefinitionTenantId(tenantId);
        }
        ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
        if (processDefinition.isSuspended()) {
            throw new BizException("流程已是挂起状态！");
        }
        repositoryService.suspendProcessDefinitionById(processDefinition.getId());
        logger.info("挂起流程成功，流程定义ID：【{}】，租户：【{}】", processDefId, tenantId);
    }

    /**
     * 激活流程
     *
     * @param processDefId
     * @param tenantId
     */
    @Transactional(rollbackFor = Exception.class)
    public void active(String processDefId, String tenantId) {
        String[] keys = processDefId.split(":");
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().processDefinitionKey(keys[0]).processDefinitionVersion(Integer.parseInt(keys[1]));
        if (StringUtil.isNotBlank(tenantId)) {
            processDefinitionQuery.processDefinitionTenantId(tenantId);
        }
        ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
        if (!processDefinition.isSuspended()) {
            throw new BizException("流程已是激活状态！");
        }
        repositoryService.activateProcessDefinitionById(processDefinition.getId());
        logger.info("激活流程成功，流程定义ID：【{}】，租户：【{}】", processDefId, tenantId);
    }

    /**
     * 查看部署定义流程图
     *
     * @param processDefId
     * @return
     */
    public InputStream getProcessDiagram(String processDefId) {
        return repositoryService.getProcessDiagram(processDefId);
    }

    /**
     * 流程xml文件
     *
     * @param processDefId
     * @return
     */
    public ResponseEntity<InputStreamResource> getProcessXml(String processDefId) {
        // 获取流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefId).singleResult();

        if (processDefinition == null) {
            return ResponseEntity.notFound().build();
        }

        // 获取流程定义的XML
        try (InputStream inputStream = repositoryService.getProcessModel(processDefinition.getId())) {

            byte[] bytes = processDefinition.getResourceName().getBytes(StandardCharsets.UTF_8);
            String fileName = new String(bytes, StandardCharsets.ISO_8859_1);

            InputStreamResource resource = new InputStreamResource(inputStream);
            HttpHeaders headers = new HttpHeaders();
            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);
            headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
            headers.add("Pragma", "no-cache");
            headers.add("Expires", "0");

            return ResponseEntity.ok()
                    .headers(headers)
//                    .contentLength(file.length())
                    .contentType(MediaType.APPLICATION_XML)
                    .body(resource);
        } catch (Exception e) {
            logger.error("流程xml生成失败", e);
            return ResponseEntity.status(500).build();
        }
    }

    /**
     * 查询流程定义的key
     *
     * @param tenantId
     * @return
     */
    public Map<String, String> getProcDefKeys(String tenantId) {
        Map<String, String> map = new HashMap<>();
        //查询激活的流程定义
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().active();
        if (StringUtil.isNotBlank(tenantId)) {
            processDefinitionQuery.processDefinitionTenantId(tenantId);
        }
        List<ProcessDefinition> definitions = processDefinitionQuery.list();
        if (CollectionUtils.isEmpty(definitions)) {
            return map;
        }
        map = definitions.stream().collect(Collectors.toMap(ProcessDefinition::getKey, ProcessDefinition::getName, (existing, replacement) -> replacement));
        return map;
    }

    /**
     * 根据流程定义ID查询所有的人工节点
     *
     * @param processDefId
     * @return
     */
    public Map<String, String> getAllUserTask(String processDefId) {
        String[] keys = processDefId.split(":");
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefId);
        Process process = bpmnModel.getProcessById(keys[0]);
        Collection<FlowElement> flowElements = process.getFlowElements();
        Iterator<FlowElement> iterator = flowElements.iterator();
        Map<String, String> tasks = new HashMap<>();
        while (iterator.hasNext()) {
            FlowElement flowElement = iterator.next();
            if (flowElement instanceof UserTask) {
                UserTask userTask = (UserTask) flowElement;
                tasks.put(userTask.getId(), userTask.getName());
            }
        }
        return tasks;
    }

    /**
     * 新增候选组
     *
     * @param groupId
     * @param name
     * @param type
     */
    @Transactional(rollbackFor = Exception.class)
    public void addGroup(String groupId, String name, String type) {
        if (Ft.String.isBlank(groupId)) {
            groupId = Ft.Snowflake.nextIdStr();
        }
        Group group = identityService.newGroup(groupId);
        group.setName(name);
        group.setType(type);
        identityService.saveGroup(group);
    }

    /**
     * 修改候选组
     *
     * @param groupId
     * @param name
     * @param type
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateGroup(String groupId, String name, String type) {
        if (Ft.String.isBlank(groupId)) {
            throw new BizException("分组ID[" + groupId + "]不能为空");
        }
        Group group = identityService.createGroupQuery().groupId(groupId).singleResult();
        if (Objects.isNull(group)) {
            throw new BizException("修改的分组ID[" + groupId + "]不存在");
        }
        if (Ft.String.isNotBlank(name)) {
            group.setName(name);
        }
        if (Ft.String.isNotBlank(type)) {
            group.setType(type);
        }
        identityService.saveGroup(group);
    }

    /**
     * 删除候选组
     *
     * @param groupId
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteGroup(String groupId) {
        if (Ft.String.isBlank(groupId)) {
            throw new BizException("分组ID[" + groupId + "]不能为空");
        }
        identityService.deleteGroup(groupId);
    }

//    /**
//     * 新增用户
//     *
//     * @param userId
//     * @param name
//     * @param type
//     */
//    @Transactional(rollbackFor = Exception.class)
//    public void addUser(String userId, String name, String type) {
//        if (Ft.String.isBlank(userId)) {
//            userId = Ft.Snowflake.nextIdStr();
//        }
//        User user = identityService.newUser(userId);
//        user.setName(name);
//        user.setType(type);
//        identityService.saveGroup(group);
//    }
//
//    /**
//     * 修改候选组
//     *
//     * @param groupId
//     * @param name
//     * @param type
//     */
//    @Transactional(rollbackFor = Exception.class)
//    public void updateGroup(String groupId, String name, String type) {
//        if (Ft.String.isBlank(groupId)) {
//            throw new BizException("分组ID[" + groupId + "]不能为空");
//        }
//        Group group = identityService.createGroupQuery().groupId(groupId).singleResult();
//        if (Objects.isNull(group)) {
//            throw new BizException("修改的分组ID[" + groupId + "]不存在");
//        }
//        if (Ft.String.isNotBlank(name)) {
//            group.setName(name);
//        }
//        if (Ft.String.isNotBlank(type)) {
//            group.setType(type);
//        }
//        identityService.saveGroup(group);
//    }
//
//    /**
//     * 删除候选组
//     *
//     * @param groupId
//     */
//    @Transactional(rollbackFor = Exception.class)
//    public void deleteGroup(String groupId) {
//        if (Ft.String.isBlank(groupId)) {
//            throw new BizException("分组ID[" + groupId + "]不能为空");
//        }
//        identityService.deleteGroup(groupId);
//    }

}
